package cn.zyl.common.redis.util;

import cn.zyl.common.redis.exception.MiniRedisDataException;
import cn.zyl.common.redis.exception.MiniRedisException;
import cn.zyl.common.redis.exception.MiniRedisRedirectionException;

import java.util.ArrayList;
import java.util.List;

/** MiniRedisTool 工具类说明: 处理函数
 * @author wsz
 * @date 2020-08-11
 * @version v1.0
 */
public class MiniRedisTool {
    private MiniRedisTool(){throw new IllegalAccessError("MiniRedisTool");}
    public static final int NE_1 = -1;
    public static final int N_0 = 0;
    public static final int N_1 = 1;
    public static final String S_EMPTY = "";
    public static final String S_SPACE = " ";
    public static final char C_COLON = ':';
    /** 异常响应前缀（询问）：*/
    private static final String ASK_PREFIX = "ASK ";
    /** 异常响应前缀（移动）：*/
    private static final String MOVED_PREFIX = "MOVED ";
    /** 异常响应前缀（集群）：*/
    private static final String CLUSTER_DOWN_PREFIX = "CLUSTERDOWN ";
    /** 异常响应前缀（忙）：服务器忙碌异常*/
    private static final String BUSY_PREFIX = "BUSY ";
    /** 异常响应前缀（无脚本）：服务器无脚本异常*/
    private static final String NO_SCRIPT_PREFIX = "NOSCRIPT ";
    /** 异常响应前缀（错误传递）：服务器访问控制异常*/
    private static final String WRONG_PASS_PREFIX = "WRONGPASS";
    /** 异常响应前缀（没有）：服务器访问控制异常*/
    private static final String NO_PERM_PREFIX = "NOPERM";
    /** redis服务器返回信息前缀 +前缀：状态码回复*/
    public  static final byte RES_STATUS = '+';
    /** redis服务器返回信息前缀 $前缀：字节数组*/
    public  static final byte RES_BYTE = '$';
    /** redis服务器返回信息前缀 *前缀：列表*/
    public  static final byte RES_LIST = '*';
    /** redis服务器返回信息前缀 :前缀：数字*/
    public  static final byte RES_INTEGER = ':';
    /** redis服务器返回信息前缀 -前缀：错误信息*/
    public  static final byte RES_ERROR = '-';

    /**
     * 分解出 host地址、port端口
     * @param from 待分解的字符串
     * @return 字符串数组，0地址，1端口
     */
    public static String[] extractParts(String from){
        int idx     = from.lastIndexOf(C_COLON);
        String host = idx != NE_1 ? from.substring(N_0, idx)  : from;
        String port = idx != NE_1 ? from.substring(idx + N_1) : S_EMPTY;
        return new String[] { host, port };
    }

    /**
     * 解析 群集重定向响应
     * @param clusterRedirectResponse 群集重定向响应信息
     * @return 提示信息、host地址、port端口
     */
    public static String[] parseTargetHostAndSlot(String clusterRedirectResponse) {
        String[] mes = clusterRedirectResponse.split(S_SPACE);
        String[] hostAndPort = extractParts(mes[2]);
        String[] res = new String[3];
        res[0] = mes[1];
        res[1] = hostAndPort[0];
        res[2] = hostAndPort[1];
        return res;
    }

    public static Object process(final MiniRedisInputStream is) throws MiniRedisException{
        final byte b = is.readByte();
        switch (b){
            case RES_STATUS:
                /* 解析响应状态码信息*/
                return is.readLineBytes();
            case RES_BYTE:
                /* 解析字节码数组*/
                return parseTargetBytes(is);
            case RES_LIST:
                /* 解析obj数组*/
                return parseTargetList(is);
            case RES_INTEGER:
                /* 解析数字内容*/
                return is.readLongCrLf();
            case RES_ERROR:
                /* 解析错误信息*/
                parseTargetError(is);
                return null;
            default:
                /* 未知答复码*/
                throw new MiniRedisException("Unknown reply: " + (char)b );
        }
    }
    /**
     * 解析异常响应信息
     * @param is 异常信息流
     * @throws MiniRedisException 抛出对应的异常信息
     */
    public static void parseTargetError(final MiniRedisInputStream is) throws MiniRedisException {
        String message = is.readLine();
        // 我不确定这是不是最好的办法。也许只读取前5个字节？
        if (message.startsWith(MOVED_PREFIX)) {
            //移动数据异常
            String[] movedInfo = parseTargetHostAndSlot(message);
            throw new MiniRedisRedirectionException("(Moved Key Exception)-_-:" + message, movedInfo[1],Integer.parseInt(movedInfo[2]), Integer.parseInt(movedInfo[0]));
        } else if (message.startsWith(ASK_PREFIX)) {
            //询问数据异常
            String[] askInfo = parseTargetHostAndSlot(message);
            throw new MiniRedisRedirectionException("(Ask Data Exception)-_-:" + message,askInfo[1],Integer.parseInt(askInfo[2]), Integer.parseInt(askInfo[0]));
        } else if (message.startsWith(CLUSTER_DOWN_PREFIX)) {
            //服务器集群异常
            throw new MiniRedisDataException("(Server Cluster Exception)-_-:" + message);
        } else if (message.startsWith(BUSY_PREFIX)) {
            //服务器忙碌异常
            throw new MiniRedisDataException("(Server Busy)-_-:" + message);
        } else if (message.startsWith(NO_SCRIPT_PREFIX)) {
            //服务器无脚本异常
            throw new MiniRedisDataException("(No Script Exception)-_-:" + message);
        } else if (message.startsWith(WRONG_PASS_PREFIX)) {
            //服务器访问控制异常
            throw new MiniRedisDataException("(Access Control Exception)-_-:" + message);
        } else if (message.startsWith(NO_PERM_PREFIX)) {
            //服务器访问控制异常
            throw new MiniRedisDataException("(Access Control Exception)-_-:" + message);
        }
        throw new MiniRedisDataException(message);
    }

    public static byte[] parseTargetBytes(final MiniRedisInputStream is){
        final int len = is.readIntCrLf();
        if (len <= NE_1) { return null; }
        final byte[] read = new byte[len];
        int offset = N_0;
        while (offset < len) {
            final int size = is.read(read, offset, (len - offset));
            if (size <= NE_1) {
                /* 服务器似乎已关闭连接*/
                throw new MiniRedisException("It seems like server has closed the connection.");
            }
            offset += size;
        }
        // 再读取2个字节作为命令分隔符
        is.readByte();
        is.readByte();
        return read;
    }
    public static List<Object> parseTargetList(final MiniRedisInputStream is){
        final int num = is.readIntCrLf();
        if (num <= NE_1) { return null; }
        final List<Object> ret = new ArrayList<>(num);
        for (int i = 0; i < num; i++) {
            try {
                ret.add(process(is));
            } catch (MiniRedisDataException e) {
                ret.add(e);
            }
        }
        return ret;
    }
}